package cn.iocoder.yudao.framework.pay.core.client.impl.kuaishou;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * 快手小程序支付客户端实现类
 *
 * @author 芋道源码
 */
@Slf4j
public class KuaishouLitePayClient extends AbstractPayClient<KuaishouPayClientConfig> {

    /**
     * 快手支付API基础URL - 正式环境
     */
    private static final String KUAISHOU_PAY_BASE_URL = "https://open.kuaishou.com/openapi/mp/developer/epay";

    /**
     * 快手支付API基础URL - 沙箱环境
     * 注：快手支付沙箱环境和正式环境使用相同的URL
     */
    private static final String KUAISHOU_PAY_SANDBOX_URL = "https://open.kuaishou.com/openapi/mp/developer/epay";

    /**
     * access_token缓存
     */
    private volatile String cachedAccessToken;

    /**
     * access_token过期时间
     */
    private volatile long tokenExpireTime;

    public KuaishouLitePayClient(Long channelId, KuaishouPayClientConfig config) {
        super(channelId, PayChannelEnum.KUAISHOU_LITE.getCode(), config);
    }

    @Override
    protected void doInit() {
        log.info("[doInit][快手支付客户端初始化完成，渠道编号：{}]", getId());
    }

    @Override
    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Exception {
        // 构建请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("open_id", getOpenid(reqDTO)); // 快手用户openid
        params.put("out_order_no", reqDTO.getOutTradeNo());
        params.put("total_amount", reqDTO.getPrice()); // 金额，单位：分
        params.put("subject", reqDTO.getSubject());
        params.put("detail", StrUtil.blankToDefault(reqDTO.getBody(), reqDTO.getSubject()));
        params.put("type", 1); // 商品类型，默认为1
        params.put("expire_time", 3600); // 订单过期时间，默认1小时

        // 添加回调地址
        if (StrUtil.isNotBlank(reqDTO.getNotifyUrl())) {
            params.put("notify_url", reqDTO.getNotifyUrl());
        }

        // 生成签名
        String sign = generateSign(params);
        params.put("sign", sign);

        // 构建完整的请求URL，包含app_id和access_token
        String baseUrl = getBaseUrl() + "/create_order";
        String url = baseUrl + "?app_id=" + config.getAppId() + "&access_token=" + getAccessToken();

        // 发起POST请求，参数放在body中
        String response = HttpRequest.post(url)
                .header("Content-Type", "application/json")
                .body(JsonUtils.toJsonString(params))
                .execute()
                .body();

        log.info("[doUnifiedOrder][快手支付统一下单响应：{}]", response);

        // 解析响应
        JSONObject responseObj = JSONUtil.parseObj(response);
        if (responseObj.getInt("result") != 1) {
            String errorMsg = responseObj.getStr("error_msg", "快手支付下单失败");
            return PayOrderRespDTO.closedOf("KUAISHOU_ERROR", errorMsg, reqDTO.getOutTradeNo(), response);
        }

        // 构建支付参数
        JSONObject orderInfo = responseObj.getJSONObject("order_info");
        String orderNo = orderInfo.getStr("order_no");
        String orderInfoToken = orderInfo.getStr("order_info_token");

        // 构建快手小程序支付参数，符合ks.pay API要求
        Map<String, Object> ksPayParams = new HashMap<>();
        ksPayParams.put("serviceId", "1"); // 固定值

        // 订单信息
        Map<String, Object> orderInfoMap = new HashMap<>();
        orderInfoMap.put("order_no", orderNo);
        orderInfoMap.put("order_info_token", orderInfoToken);
        ksPayParams.put("orderInfo", orderInfoMap);

        // 注意：paymentChannel 是可选的，如果需要指定支付渠道可以在前端添加

        return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(),
                JsonUtils.toJsonString(ksPayParams), reqDTO.getOutTradeNo(), response);
    }

    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws Exception {
        log.info("[doParseOrderNotify][快手支付回调参数：{}，body：{}]", params, body);

        // 注意：快手的签名是通过HTTP header传递的，这里params中应该包含header信息
        // 如果params中没有kwaisign，说明调用方没有正确传递header信息
        String receivedSign = params.get("kwaisign");
        if (StrUtil.isBlank(receivedSign)) {
            log.error("[doParseOrderNotify][快手支付回调缺少签名，请检查是否正确传递kwaisign header]");
            throw new IllegalArgumentException("快手支付回调缺少签名");
        }

        // 验证签名
        if (!verifyNotifySign(body, receivedSign)) {
            log.error("[doParseOrderNotify][快手支付回调签名验证失败]");
            throw new IllegalArgumentException("快手支付回调签名验证失败");
        }

        // 解析回调数据
        JSONObject notifyData = JSONUtil.parseObj(body);

        // 检查业务类型
        String bizType = notifyData.getStr("biz_type");
        if (!"PAYMENT".equals(bizType)) {
            log.warn("[doParseOrderNotify][非支付回调，业务类型：{}]", bizType);
            throw new IllegalArgumentException("非支付回调");
        }

        JSONObject data = notifyData.getJSONObject("data");
        if (data == null) {
            log.error("[doParseOrderNotify][回调数据中缺少data字段]");
            throw new IllegalArgumentException("回调数据格式错误");
        }

        // 解析支付状态
        String status = data.getStr("status");
        Integer payStatus = parseOrderStatus(status);

        // 解析支付时间
        LocalDateTime successTime = null;
        if (PayOrderStatusRespEnum.isSuccess(payStatus)) {
            Long timestamp = notifyData.getLong("timestamp");
            if (timestamp != null) {
                successTime = LocalDateTimeUtil.of(timestamp);
            }
        }

        // 获取channelUserId：快手支付回调中open_id通常为null，需要从原始支付请求中获取
        String channelUserId = data.getStr("open_id");
        if (StrUtil.isBlank(channelUserId)) {
            // 快手支付回调中open_id为null是正常现象，特别是使用微信渠道支付时
            // 这里可以考虑从支付订单扩展信息中获取，或者设置为null让上层业务处理
            log.warn("[doParseOrderNotify][快手支付回调中open_id为空，订单号：{}]", data.getStr("out_order_no"));
            channelUserId = null; // 明确设置为null，让上层业务知道这是预期行为
        }

        return PayOrderRespDTO.of(payStatus, data.getStr("ks_order_no"),
                channelUserId, successTime, data.getStr("out_order_no"), body);
    }

    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo) throws Exception {
        // 构建查询参数
        Map<String, Object> params = new HashMap<>();
        params.put("out_order_no", outTradeNo);

        // 生成签名
        String sign = generateSign(params);
        params.put("sign", sign);

        // 构建完整的请求URL，包含app_id和access_token
        String baseUrl = getBaseUrl() + "/query_order";
        String url = baseUrl + "?app_id=" + config.getAppId() + "&access_token=" + getAccessToken();

        // 发起POST请求，参数放在body中
        String response = HttpRequest.post(url)
                .header("Content-Type", "application/json")
                .body(JsonUtils.toJsonString(params))
                .execute()
                .body();
        
        log.info("[doGetOrder][快手支付订单查询响应：{}]", response);

        // 解析响应
        JSONObject responseObj = JSONUtil.parseObj(response);
        if (responseObj.getInt("result") != 1) {
            return PayOrderRespDTO.closedOf("KUAISHOU_ERROR", "订单查询失败", outTradeNo, response);
        }

        JSONObject data = responseObj.getJSONObject("data");
        String status = data.getStr("status");
        Integer payStatus = parseOrderStatus(status);
        
        // 解析支付时间
        LocalDateTime successTime = null;
        if (PayOrderStatusRespEnum.isSuccess(payStatus)) {
            Long payTime = data.getLong("pay_time");
            if (payTime != null) {
                successTime = LocalDateTimeUtil.of(payTime * 1000);
            }
        }

        return PayOrderRespDTO.of(payStatus, data.getStr("order_id"), 
                data.getStr("openid"), successTime, outTradeNo, response);
    }

    @Override
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Exception {
        // 快手支付退款功能暂未实现
        log.warn("[doUnifiedRefund][快手支付暂不支持退款功能]");
        return PayRefundRespDTO.failureOf("NOT_SUPPORTED", "快手支付暂不支持退款功能", 
                reqDTO.getOutRefundNo(), "");
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws Exception {
        // 快手支付退款回调暂未实现
        throw new UnsupportedOperationException("快手支付暂不支持退款回调");
    }

    @Override
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws Exception {
        // 快手支付退款查询暂未实现
        throw new UnsupportedOperationException("快手支付暂不支持退款查询");
    }

    @Override
    protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Exception {
        // 快手支付转账功能暂未实现
        throw new UnsupportedOperationException("快手支付暂不支持转账功能");
    }

    @Override
    protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws Exception {
        // 快手支付转账查询暂未实现
        throw new UnsupportedOperationException("快手支付暂不支持转账查询");
    }

    @Override
    protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Exception {
        // 快手支付转账回调暂未实现
        throw new UnsupportedOperationException("快手支付暂不支持转账回调");
    }

    /**
     * 获取快手支付API基础URL
     */
    private String getBaseUrl() {
        return "sandbox".equals(config.getEnvironment()) ? KUAISHOU_PAY_SANDBOX_URL : KUAISHOU_PAY_BASE_URL;
    }

    /**
     * 获取access_token（带缓存）
     * 调用快手开放平台的getAccessToken接口获取
     */
    private String getAccessToken() {
        // 检查缓存是否有效（提前5分钟刷新）
        long currentTime = System.currentTimeMillis();
        if (StrUtil.isNotBlank(cachedAccessToken) && currentTime < (tokenExpireTime - 300000)) {
            return cachedAccessToken;
        }

        // 获取新的access_token
        synchronized (this) {
            // 双重检查
            if (StrUtil.isNotBlank(cachedAccessToken) && currentTime < (tokenExpireTime - 300000)) {
                return cachedAccessToken;
            }

            try {
                // 构建请求URL，快手使用GET请求，参数放在URL中
                String url = "https://open.kuaishou.com/oauth2/access_token" +
                        "?app_id=" + config.getAppId() +
                        "&app_secret=" + config.getAppSecret() +
                        "&grant_type=client_credentials";

                // 发起GET请求
                String response = HttpRequest.get(url)
                        .execute()
                        .body();

                log.info("[getAccessToken][快手获取access_token响应：{}]", response);

                // 解析响应
                JSONObject responseObj = JSONUtil.parseObj(response);
                if (responseObj.getInt("result") == 1) {
                    String accessToken = responseObj.getStr("access_token");
                    Integer expiresIn = responseObj.getInt("expires_in", 7200); // 默认2小时

                    // 更新缓存
                    this.cachedAccessToken = accessToken;
                    this.tokenExpireTime = currentTime + (expiresIn * 1000L);

                    log.info("[getAccessToken][成功获取快手access_token，过期时间：{}]",
                            new Date(tokenExpireTime));

                    return accessToken;
                } else {
                    String errorMsg = responseObj.getStr("error_description", "获取access_token失败");
                    log.error("[getAccessToken][获取快手access_token失败：{}]", errorMsg);
                    throw new RuntimeException("获取快手access_token失败：" + errorMsg);
                }
            } catch (Exception e) {
                log.error("[getAccessToken][获取快手access_token异常]", e);
                throw new RuntimeException("获取快手access_token异常：" + e.getMessage());
            }
        }
    }

    /**
     * 从请求参数中获取openid
     */
    private String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
        Map<String, String> channelExtras = reqDTO.getChannelExtras();
        if (channelExtras != null) {
            return channelExtras.get("openid");
        }
        return null;
    }

    /**
     * 生成签名 - 按照快手官方文档规范
     */
    private String generateSign(Map<String, Object> params) {
        // 添加app_id到签名参数中
        Map<String, Object> signParams = new HashMap<>(params);
        signParams.put("app_id", config.getAppId());

        // 过滤空值和sign字段
        Map<String, Object> filteredParams = signParams.entrySet()
                .stream()
                .filter(entry -> !"sign".equals(entry.getKey()) &&
                               !"access_token".equals(entry.getKey()) &&
                               entry.getValue() != null &&
                               !"".equals(entry.getValue().toString().trim()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        // 按照字母排序
        Map<String, Object> sortedParams = filteredParams.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue,
                        LinkedHashMap::new));

        // 构建签名字符串
        StringBuilder signStr = new StringBuilder();
        for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
            if (signStr.length() > 0) {
                signStr.append("&");
            }
            signStr.append(entry.getKey()).append("=").append(entry.getValue());
        }

        // 按照快手官方文档：拼接app_secret
        signStr.append(config.getAppSecret());

        log.debug("[generateSign][待签名字符串：{}]", signStr.toString());

        // MD5加密并转小写（快手官方文档示例为小写）
        return MD5.create().digestHex(signStr.toString()).toLowerCase();
    }

    /**
     * 验证回调签名 - 按照快手官方文档规范
     */
    private boolean verifyNotifySign(String body, String receivedSign) {
        if (StrUtil.isBlank(receivedSign) || StrUtil.isBlank(body)) {
            return false;
        }

        // 按照快手官方文档：MD5(${http_body_string} + ${app_secret})
        String signStr = body + config.getAppSecret();
        String calculatedSign = MD5.create().digestHex(signStr).toLowerCase();

        log.debug("[verifyNotifySign][待验签字符串：{}，计算签名：{}，接收签名：{}]",
                signStr, calculatedSign, receivedSign);

        return receivedSign.toLowerCase().equals(calculatedSign);
    }

    /**
     * 解析订单状态
     */
    private Integer parseOrderStatus(String status) {
        switch (status) {
            case "SUCCESS":
                return PayOrderStatusRespEnum.SUCCESS.getStatus();
            case "CLOSED":
            case "FAILED":
                return PayOrderStatusRespEnum.CLOSED.getStatus();
            case "WAITING":
            default:
                return PayOrderStatusRespEnum.WAITING.getStatus();
        }
    }
}
